Skip to content

fix: apply single_distinct_to_groupby to aliased DataFrame API aggregates#23403

Open
fangchenli wants to merge 2 commits into
apache:mainfrom
fangchenli:fix/single-distinct-dataframe-alias
Open

fix: apply single_distinct_to_groupby to aliased DataFrame API aggregates#23403
fangchenli wants to merge 2 commits into
apache:mainfrom
fangchenli:fix/single-distinct-dataframe-alias

Conversation

@fangchenli

@fangchenli fangchenli commented Jul 9, 2026

Copy link
Copy Markdown

Which issue does this PR close?

Rationale for this change

The SingleDistinctToGroupBy optimizer rule fires for the SQL planner but not for the logically-equivalent plan built by the DataFrame API, because the two front-ends place the output alias differently:

  • SQL (count(DISTINCT v) AS n) keeps Aggregate.aggr_expr bare and carries the alias in an outer Projection.
  • DataFrame API (count(col("v")).distinct().alias("n")) applies the alias directly to the aggregate, so aggr_expr holds Expr::Alias(AggregateFunction).

is_single_distinct_agg only matched a bare Expr::AggregateFunction, so the aliased shape fell through and the rule never fired. As a result the DataFrame API path was materially slower than the identical SQL query.

What changes are included in this PR?

  • is_single_distinct_agg: peel any top-level Expr::Alias wrapper(s) before matching Expr::AggregateFunction, so aliased aggregates built by the DataFrame API are recognized. Uses a small borrowed helper unalias_ref.
  • Rewrite body: reuse the existing datafusion_expr::expr_rewriter::unalias to strip the outer alias(es) before processing each aggregate. Output column names are preserved automatically, since the rule already rebuilds the outer Projection from the original aggregate's schema field names.
  • work around GROUP BY constant with aggregation function and with empty input magically summons a row #15734

Are these changes tested?

Yes, unit tests added.

Are there any user-facing changes?

No behavior change, except performance improvement.

@github-actions github-actions Bot added the optimizer Optimizer rules label Jul 9, 2026
@fangchenli fangchenli force-pushed the fix/single-distinct-dataframe-alias branch 2 times, most recently from 1aa3e4c to 811088a Compare July 9, 2026 04:40
@kumarUjjawal

Copy link
Copy Markdown
Contributor

@fangchenli if you could fix the failing tests, I will review soon.

@fangchenli fangchenli force-pushed the fix/single-distinct-dataframe-alias branch from 2712985 to c388b94 Compare July 9, 2026 06:25
@github-actions github-actions Bot added the sqllogictest SQL Logic Tests (.slt) label Jul 9, 2026
…ates

The SingleDistinctToGroupBy rule rewrites grouped count(DISTINCT col) into a
nested double aggregate, but only fired for SQL-planned plans. The DataFrame
API applies the output alias directly to the aggregate expression, so
aggr_expr holds Expr::Alias(AggregateFunction); is_single_distinct_agg only
matched a bare Expr::AggregateFunction and the rule never fired, leaving the
DataFrame path materially slower than the equivalent SQL.

Peel all top-level Expr::Alias wrappers in both the matcher and the rewrite
(aliases nested inside sub-expressions are left untouched). Output column
names are preserved because the rule already rebuilds the outer projection
from the original aggregate's schema. Mirrors the fix for the same bug class
in apache#21411 / apache#21438.

Also skip the rewrite when a DISTINCT argument is a constant. Such an argument
would become a constant GROUP BY key that eliminate_group_by_constant collapses
into a global aggregate, which emits a row even on empty input and produces
wrong results (e.g. MIN(DISTINCT 37) over empty input returned 37, not NULL).
This is a latent bug the alias fix would otherwise expose more widely; the
rewrite has no benefit for a constant argument anyway.

Closes apache#23401.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fangchenli fangchenli force-pushed the fix/single-distinct-dataframe-alias branch from c388b94 to 7975288 Compare July 9, 2026 06:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

single_distinct_to_groupby not applied to DataFrame API count(DISTINCT)

2 participants